# 当日的走势, 只处理盘中
# TODO 之前做的管线, 是为了更快取得数据,但是在这里异步, 上层调用不知道异步, 这造成这里会有一帧返回None, 然后会有一帧返回数据
#    先去掉异步, 看上层调用会不会简单一些, 因为同一只股票分析过一次后就不用再次分析了.
#    问题是界面是固定的, 格子不会动, 分析出格子的那部分可以一直不变. 可是text_region会被设置为None. 这个BUG的关键在于输入到线程的数据源是debug_frame而不是frame. debug_frame会受到修改

# from concurrent.futures import ThreadPoolExecutor
import cv2
import numpy as np

from shape_judgment.find_right_of_k_line_area import find_right_of_k_line_area
from shape_judgment.find_top_left_of_k_line_area import find_top_left_of_k_line_area
from shape_judgment.find_bottom_of_k_line_area import find_bottom_of_k_line_area
from shape_judgment.cut_off_main_chart import extract_main_chart
from shape_judgment.get_k_line_data_from_image import extract_price_coordinates
from shape_judgment.figure_out_pos_of_separator_line import figure_out_pos_of_separator_line
from shape_judgment.get_stock_id import get_stock_id
from shape_judgment.write_to_file import write_line_to_file
import logging


class trading_session:
    def __init__(self):
        # self.executor = ThreadPoolExecutor(max_workers=16)  # 线程池（2个工作线程）
        # self.active_tasks = {}  # 存储活跃任务  在任务完成时自动删除任务记录
        self.text_region = None
        self.top_left_button = None # 左上角的打叉按钮
        self.bottom_position = None # 包过副图, 用11:30这几个字作锚
    
    def resize_to_same_width(self,img, target_width):
        """将图像调整为目标宽度，高度按比例缩放"""
        h, w = img.shape[:2]
        if w == 0:
            return img
        scale = target_width / w
        new_h = int(h * scale)
        return cv2.resize(img, (target_width, new_h))
    
    # 垂直排列图片
    def stack_images_vertically(self,images):
        """
        垂直拼接多个图像（宽度统一，垂直排列）
        :param images: 图像列表（BGR格式）
        :return: 拼接后的大图
        """
        if not images:
            return None
        
        # 统一所有图像的宽度（以最宽的图像为标准）
        max_width = max(img.shape[1] for img in images)
        # 调整所有图像到相同宽度
        resized_images = [self.resize_to_same_width(img, max_width) for img in images]
        
        # 垂直拼接（使用numpy的vstack）
        stacked = np.vstack(resized_images)
        return stacked

    def find_right_of_k_line_area(self, screen_img):
        # 记录已建立些任务, 任务名"find_right_of_k_line_area"
        self.active_tasks["find_right_of_k_line_area"]=True
        return find_right_of_k_line_area(screen_img)
    # K线右侧专用的回调函数
    def CB_find_right_of_k_line_are(self, future):
        """处理异步任务完成时调用的回调函数 任务名"find_right_of_k_line_area" 删除任务"""        
        self.active_tasks["find_right_of_k_line_area"]=False
        self.text_region = future.result()
        # print(f"# 66 {self.text_region}")

    # 找K线区域左上角 也就是集合竞价的打叉按钮
    def find_top_left_of_k_line_area(self, screen_img):
        # 记录已建立些任务, 任务名"find_top_left_of_k_line_area"
        self.active_tasks["find_top_left_of_k_line_area"]=True
        return find_top_left_of_k_line_area(screen_img)

    # 找完K线区域左上角 不管有没有找到, 进行回调
    def CB_find_top_left_of_k_line_area(self, future):
        """处理异步任务完成时调用的回调函数 删除任务"""        
        self.active_tasks["find_top_left_of_k_line_area"]=False
        self.top_left_button = future.result()

    # 找K线区的下方
    def find_bottom_of_k_line_area(self, screen_img):
        # 记录已建立些任务, 任务名"find_bottom_of_k_line_area"
        self.active_tasks["find_bottom_of_k_line_area"]=True
        return find_bottom_of_k_line_area(screen_img)
    
    # 找K线区下方的回调函数
    def CB_find_bottom_of_k_line_area(self, future):
        """处理异步任务完成时调用的回调函数 删除任务"""        
        self.active_tasks["find_bottom_of_k_line_area"]=False
        self.bottom_position = future.result()

    # 截取大图中的一小部分
    def capture_kline_region(self, full_screen_frame, x1, y1, x2, y2):
        """
        从完整窗口截图中截取K线区域
        :param full_screen_frame: 原始完整窗口截图（BGR格式，OpenCV图像）
        :param x1: K线区域左上角X坐标（相对完整窗口的局部坐标）
        :param y1: K线区域左上角Y坐标（相对完整窗口的局部坐标）
        :param x2: K线区域右下角X坐标（相对完整窗口的局部坐标）
        :param y2: K线区域右下角Y坐标（相对完整窗口的局部坐标）
        :return: K线区域独立图像（BGR格式，OpenCV图像），失败返回None
        """
        try:
            # 1. 校验输入参数有效性
            # 校验原始截图是否为空
            if full_screen_frame is None or full_screen_frame.size == 0:
                logging.error("截取K线失败：原始完整窗口截图为空")
                return None
            
            # 校验坐标是否为有效数值
            if not (
                    (isinstance(x1, int) or isinstance(x1, np.integer)) and
                    (isinstance(y1, int) or isinstance(y1, np.integer)) and
                    (isinstance(x2, int) or isinstance(x2, np.integer)) and
                    (isinstance(y2, int) or isinstance(y2, np.integer))
                ):
                logging.error(f"截取K线失败：坐标格式错误（x1={x1}, y1={y1}, x2={x2}, y2={y2}）")
                return None
            
            # 2. 计算K线区域的宽高，校验坐标逻辑合理性（避免x2<x1或y2<y1）
            kline_width = x2 - x1
            kline_height = y2 - y1
            if kline_width <= 0 or kline_height <= 0:
                logging.error(f"截取K线失败：坐标逻辑错误，宽高不能为负（宽={kline_width}, 高={kline_height}）")
                # 自动修正坐标顺序（确保x1<x2、y1<y2）
                x1, x2 = min(x1, x2), max(x1, x2)
                y1, y2 = min(y1, y2), max(y1, y2)
                kline_width = x2 - x1
                kline_height = y2 - y1
                logging.warning(f"已自动修正坐标为（x1={x1}, y1={y1}, x2={x2}, y2={y2}）")
            
            # 3. 校验K线区域是否超出原始截图范围（避免越界报错）
            frame_height, frame_width = full_screen_frame.shape[:2]  # 原始截图的高、宽（OpenCV格式：height在前，width在后）
            # 修正超出边界的坐标（确保截取范围在原始截图内）
            x1 = max(0, min(x1, frame_width - 1))
            x2 = max(x1 + 1, min(x2, frame_width))
            y1 = max(0, min(y1, frame_height - 1))
            y2 = max(y1 + 1, min(y2, frame_height))
            
            # 再次校验修正后的宽高（防止极端情况下修正后仍无效）
            final_width = x2 - x1
            final_height = y2 - y1
            if final_width <= 0 or final_height <= 0:
                logging.error(f"截取K线失败：修正后坐标仍无效（宽={final_width}, 高={final_height}）")
                return None
            
            # 4. 从原始截图中截取K线区域（OpenCV图像切片：[y1:y2, x1:x2]，注意y在前、x在后）
            kline_region = full_screen_frame[y1:y2, x1:x2].copy()  # 用copy()避免原始图像被修改
            
            # # 5. 日志记录截取成功信息
            # logging.info(f"K线区域截取成功：位置（x1={x1}, y1={y1}, x2={x2}, y2={y2}），尺寸（{final_width}×{final_height}）")
            
            return kline_region

        except Exception as e:
            # 捕获所有异常，避免程序崩溃
            logging.error(f"截取K线区域时发生未知错误：{str(e)}", exc_info=True)
            return None


    def calculate(self,frame,debug_frame):
        # return: 返回一个参数列表, 由键值对组成 需要返回三种状态 1:还在分析 2:退市股票 3:分析出的参数列表
        #   把退市状态放到上层调用去做, 就不用每个分析模块都做一遍

        # 任务名"find_right_of_k_line_area" 没在执行任务的话, 就执行任务
        self.text_region = find_right_of_k_line_area(frame)
        # if self.active_tasks.get("find_right_of_k_line_area") == False or "find_right_of_k_line_area" not in self.active_tasks:
        #     future = self.executor.submit(self.find_right_of_k_line_area, frame )
        #     future.add_done_callback(self.CB_find_right_of_k_line_are)
        # 任务名"find_top_left_of_k_line_area"
        self.top_left_button = find_top_left_of_k_line_area(frame)
        # if self.active_tasks.get("find_top_left_of_k_line_area") == False or "find_top_left_of_k_line_area" not in self.active_tasks:
        #     future = self.executor.submit(self.find_top_left_of_k_line_area, frame )
        #     future.add_done_callback(self.CB_find_top_left_of_k_line_area)
        # 任务名"find_bottom_of_k_line_area"
        self.bottom_position = find_bottom_of_k_line_area(frame)
        # if self.active_tasks.get("find_bottom_of_k_line_area") == False or "find_bottom_of_k_line_area" not in self.active_tasks:
        #     future = self.executor.submit(self.find_bottom_of_k_line_area, frame )
        #     future.add_done_callback(self.CB_find_bottom_of_k_line_area)

        # # 如果没找到"日K"，尝试"周K"
        # if not text_region:
        #     text_region = self.find_region_by_text(frame, "周K")
        
        if self.text_region:
            # 在调试帧上绘制文字区域
            x, y, w, h = self.text_region
            cv2.rectangle(debug_frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
            cv2.putText(debug_frame, "found target word", (x, y - 10), 
                        cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)
        if self.top_left_button:
            x, y, w, h = self.top_left_button
            color_blue = (255, 0, 0)
            cv2.rectangle(debug_frame, (x, y), (x + w, y + h), color_blue, 2)
            # print(f"# 301 {x, y, w, h}")
            # cv2.putText(debug_frame, "found target button", (x, y - 10), 
            #             cv2.FONT_HERSHEY_SIMPLEX, 0.9, color_blue, 2)
        if self.bottom_position:
            x, y, w, h = self.bottom_position
            color_red = (0, 0, 255)
            cv2.rectangle(debug_frame, (x, y), (x + w, y + h), color_red, 2)
            cv2.putText(debug_frame, "found 11:30", (x, y - 10), 
                        cv2.FONT_HERSHEY_SIMPLEX, 0.9, color_red)
        
        if self.text_region and self.top_left_button is not None and len(self.top_left_button) >= 2 and self.bottom_position:
            self.k_line_appear = True
            x1 = self.top_left_button[0]
            x2 = self.text_region[0]
            y1 = self.top_left_button[1]
            y2 = self.bottom_position[1]
            kline_img = self.capture_kline_region(
                    full_screen_frame=frame,
                    x1=self.top_left_button[0],
                    y1=self.top_left_button[1],
                    x2=self.text_region[0],
                    y2=self.bottom_position[1]
            )
            # x = x1, y = y1, w = x2 - x1, h = y2 - y1
            color_purple = (155, 0, 155)
            cv2.rectangle(debug_frame, (x1, y1), (x2, y2), color_purple, 4)
            cv2.putText(debug_frame, "K line area", (x1, y1 - 10), 
                        cv2.FONT_HERSHEY_SIMPLEX, 0.9, color_purple)
            stock_id = get_stock_id( [ x2 , y1 ] ,frame) # 第一个参数是为了减少计算范围
            # print(f"#349 stock_id:{stock_id}")
            
            datas=[]

            if(debug_frame is not None and kline_img is not None):
                img2 = extract_main_chart(kline_img)
                img3 = None
                if None is img2:
                    img2 = kline_img
                else: # 提取价格坐标数组
                    first_x,last_x = figure_out_pos_of_separator_line(img2)
                    if first_x != last_x:
                        img3 = img2[:, first_x:last_x].copy()
                        if None is img3:
                            print("# ERR 361    None is img3")
                        price_ys, have_data =  extract_price_coordinates(img3)
                        # print( type( have_data), have_data)
                        if None is price_ys:
                            print("# ERR 364    None is price_ys")
                            print(f"first_x {first_x},last_x {last_x}")

                        # 定义点的属性
                        point_size = 1
                        point_color = (0, 0, 255) # BGR 颜色
                        len_of_ys = len(price_ys)
                        max_price = max(price_ys)
                        min_price = min(price_ys)
                        first_price = price_ys[0]
                        last_price = price_ys[len_of_ys - 1]
                        for i in range(len_of_ys):
                            price = price_ys[i]
                            thickness = 4 # 线条粗细
                            # 绘制点, 用于判断价格线的获取是否有效
                            cv2.circle(img3, [i,price], point_size, point_color, thickness)
                        # 判断穿头线:
                        delta_price = max([first_price-last_price,last_price-first_price])
                        delta_price_2 = max_price - max( [first_price,last_price])
                        height = img3.shape[0] # 图像高度
                        has_special_line = False
                        break_foot_step_1 = 0
                        break_foot_length = delta_price_2
                        if(delta_price_2/3 > delta_price):
                            # print(f"### 找到破脚线 起始值:{first_price}\t 最终值:{last_price}\t 差值:{delta_price}\t 差值2:{delta_price_2}\t图片高:{height}")
                            break_foot_step_1 = delta_price_2
                        # 判断破脚线:
                        delta_price_3 =  min([first_price,last_price]) - min_price
                        break_head_step_1 = 0
                        break_head_length = delta_price_3
                        if( delta_price_3>0 and delta_price_3/3 > delta_price):
                            # print(f"### 找到穿头线 起始值:{first_price}\t 最终值:{last_price}\t 最大值:{max_price} 最小值:{min_price} 差值:{delta_price}\t 差值3:{delta_price_3}\t图片高:{height}")
                            break_head_step_1 = delta_price_3
                        if( break_foot_step_1/3>break_head_length):
                            has_special_line = True
                            print(f"### 找到破脚线 起始值:{first_price}\t 最终值:{last_price}\t 差值:{delta_price}\t 差值2:{delta_price_2}\t图片高:{height}\t穿头长度:{break_head_length}\t破脚长度:{break_foot_length}")
                            datas.append(("穿头破脚线","破脚线"+str(delta_price_2/height)))
                        elif( break_head_step_1/3>break_foot_length):
                            has_special_line = True
                            print(f"### 找到穿头线 起始值:{first_price}\t 终值:{last_price}\t 差值:{delta_price}\t 差值3:{delta_price_3}\t图片高:{height}")
                            datas.append(("穿头破脚线","穿头线"+str(delta_price_3/height)))
                            write_line_to_file("out_file.csv",stock_id+","+"穿头线"+","+str(delta_price_3/height)+"\n")
                        
                        print("# 137 盘中分时图分析完成")

                        # bool_is_delist=False
                        # if have_data == False:
                        #     bool_is_delist = is_delist(debug_frame)
                        
                        # # TODO 需处理开盘价与收盘价一样的情况 一方面,是图像分析的BUG, 另一方面, delta_price会是零
                        # if has_special_line == False:
                        #     # print(f"没找到特殊线型\t穿头长度:{break_head_length}\t破脚长度:{break_foot_length}\t实线长度:{delta_price}")
                        #     # 不用记录, 下一页
                        #     if have_data or bool_is_delist:
                        #         print(f"# 415 下一页 有数据:{have_data} \t已退市:{bool_is_delist}")
                        #         pyautogui.press('pagedown')
                        # else:
                        #     # 记录完下一页
                        #     if have_data:
                        #         print("# 419 下一页")
                        #         pyautogui.press('pagedown')
                # 之前旧版的调试信息显示
                img_list = [debug_frame]
                if img3 is not None:
                    img_list.append(img3)
                stacked = self.stack_images_vertically(img_list)
                debug_frame = stacked
                return datas
                # return stacked
            else:
                print("# ERR 326 ","\tkline_img",kline_img)
        else:
            print(f"# 300 还没分析出区域 {self.text_region}")
        return None # 返回为空, 就是还没刷出数据
    